import { useControllableValue, useThrottleFn, useUpdateLayoutEffect } from 'ahooks';
import classNames from 'classnames';
import React, { useLayoutEffect, useRef } from 'react';
import { useSpring, animated } from '@react-spring/web';
import { useResizeEffect } from '@/hooks/useResizeEffect';
import { useMutationEffect } from '@/hooks/useMutationEffect';
import traverseReactNode from './traverseReactNode';

export type TabsProps = {
  defaultValue?: string | null;
  value?: number | string | null;
  onChange?: (val: number | string) => void;
  children?: React.ReactNode;
  className?: string;
  style?: React.CSSProperties;
  scrollClassName?: string;
};

const Tabs: React.FC<TabsProps> = ({ scrollClassName, className, style, children, ...restProps }) => {
  const panes: React.ReactElement[] = [];
  const keyToIndexRecord: Record<string, number> = {};
  let firstActiveKey: string | null = null;
  traverseReactNode(children, (child, index) => {
    if (!React.isValidElement(child)) return;
    const key = child.key;
    if (typeof key !== 'string') return;
    if (index === 0) {
      firstActiveKey = key;
    }
    const length = panes.push(child);
    keyToIndexRecord[key] = length - 1;
  });
  const [value, setValue] = useControllableValue<string>({
    ...restProps,
    defaultValue: restProps.defaultValue ?? firstActiveKey,
  });
  const scrollRef = useRef<HTMLDivElement>(null);
  const [{ leftMaskOpacity, rightMaskOpacity }, maskApi] = useSpring(() => ({
    leftMaskOpacity: 0,
    rightMaskOpacity: 0,
    config: {
      clamp: true,
    },
  }));
  const { run: updateMask } = useThrottleFn(
    (immediate = false) => {
      const container = scrollRef.current;
      if (!container) return;
      const scrollLeft = container.scrollLeft;
      const showLeftMask = scrollLeft > 0;
      const showRightMask = scrollLeft + container.offsetWidth < container.scrollWidth;
      maskApi.start({
        leftMaskOpacity: showLeftMask ? 1 : 0,
        rightMaskOpacity: showRightMask ? 1 : 0,
        immediate: !!immediate,
      });
    },
    {
      wait: 100,
      trailing: true,
      leading: true,
    },
  );
  useLayoutEffect(() => {
    updateMask(true);
  }, []);

  const [{ scrollLeft }, scrollApi] = useSpring(() => ({
    scrollLeft: 0,
    config: {
      tension: 300,
      clamp: true,
    },
  }));
  function animate(immediate = false, fromMutation = false) {
    const container = scrollRef.current;
    if (!container) return;

    const activeIndex = keyToIndexRecord[value];
    if (activeIndex === undefined) return;

    const activeTabWrapper = container.children.item(activeIndex) as HTMLDivElement;
    const activeTab = activeTabWrapper.children.item(0) as HTMLDivElement;
    const activeTabLeft = activeTab.offsetLeft;
    const activeTabWidth = activeTab.offsetWidth;

    const containerWidth = container.offsetWidth;
    const containerScrollWidth = container.scrollWidth;
    const containerScrollLeft = container.scrollLeft;

    const maxScrollDistance = containerScrollWidth - containerWidth;
    if (maxScrollDistance <= 0) return;

    let nextScrollLeft = activeTabLeft - (containerWidth - activeTabWidth) / 2;
    nextScrollLeft = Math.min(Math.max(nextScrollLeft, 0), maxScrollDistance);

    if (!fromMutation) {
      scrollApi.start({
        scrollLeft: nextScrollLeft,
        from: { scrollLeft: containerScrollLeft },
        immediate,
      });
    }
  }
  useLayoutEffect(() => {
    animate(true);
  }, []);
  useUpdateLayoutEffect(() => {
    animate();
  }, [value]);
  useResizeEffect(() => {
    animate(true);
  }, scrollRef);
  useMutationEffect(
    () => {
      animate(true, true);
    },
    scrollRef,
    {
      subtree: true,
      childList: true,
      characterData: true,
    },
  );

  return (
    <div style={style} className={classNames('relative min-w-0', className)}>
      <animated.div
        className={classNames(
          'absolute top-0 bottom-0 z-[1] w-20 h-full pointer-events-none',
          'left-0 bg-gradient-to-r from-bgc-0/90 to-bgc-0/0',
        )}
        style={{ opacity: leftMaskOpacity }}
      />
      <animated.div
        className={classNames(
          'absolute top-0 bottom-0 z-[1] w-20 h-full pointer-events-none',
          'right-0 bg-gradient-to-l from-bgc-0/90 to-bgc-0/0',
        )}
        style={{ opacity: rightMaskOpacity }}
      />
      <animated.div
        className={classNames(
          'flex flex-nowrap align-items justify-start relative',
          'overflow-x-auto space-x-8',
          scrollClassName,
        )}
        ref={scrollRef}
        onScroll={updateMask}
        scrollLeft={scrollLeft}
      >
        {panes.map((pane) => {
          return (
            <div
              className={classNames('flex-none')}
              key={pane.key}
              onClick={() => {
                if (pane.key === undefined || pane.key === null) return;
                setValue(pane.key.toString());
              }}
            >
              {pane}
            </div>
          );
        })}
      </animated.div>
    </div>
  );
};

const NTabs = React.memo(Tabs);
export default NTabs;
